
#include "tl_common.h"
#include "driver/usbstd/usbcdc.h"
#include "freeRTOS.h"
// #include "semphr.h"

///////////////  AMAZON AMA interface //////////////////
#include "hal_auxdev.h"
#if(1)
static aceAuxDevHal_hotPlugCb_t g_usbcdc_hotPlgCb;
static aceAuxDevHal_notifyCb_t g_usbcdc_notifyCb;
static int usbcdc_initialize;

static ace_status_t usbcdc_open(const aceAuxDevHal_device_t* dev);
static ace_status_t usbcdc_close(const aceAuxDevHal_device_t* dev);
static int32_t usbcdc_write(const aceAuxDevHal_device_t* dev, const void* buf, size_t buf_size, uint32_t timeout_ms, uint32_t flags);

ace_status_t usbcdc_registerNotify(const aceAuxDevHal_device_t* dev, aceAuxDevHal_notifyCb_t callback, const void* ctx){
	if(ACE_AUXDEV_HAL_AMA == dev->dev_type){
		g_usbcdc_notifyCb = callback;
	}
	return 0;
}

/** Deregister notifications function pointer. */
ace_status_t usbcdc_deregisterNotify(const aceAuxDevHal_device_t* dev){
	if(ACE_AUXDEV_HAL_AMA == dev->dev_type){
		g_usbcdc_notifyCb = 0;
	}
	return 0;
}

static aceAuxDevHal_commonApi_t usbcdc_commonapi = {
	usbcdc_open, usbcdc_close, 
	usbcdc_registerNotify, 
	usbcdc_deregisterNotify,
	0, usbcdc_write, 0
};

static aceAuxDevHal_device_t g_usbDev = {
	ACE_AUXDEV_HAL_AMA, 0, 0,
	&usbcdc_commonapi, 0, 
	"ttyACM1", "Telink"
	
		
};
static aceAuxDevHal_device_t *pg_usbDev;
static ace_status_t usbcdc_open(const aceAuxDevHal_device_t* dev){
	if(ACE_AUXDEV_HAL_AMA == dev->dev_type){
		pg_usbDev = &g_usbDev;
		return ACE_STATUS_OK;
	}
	return ACE_STATUS_BAD_PARAM;
	
}

static ace_status_t usbcdc_close(const aceAuxDevHal_device_t* dev){
	if(ACE_AUXDEV_HAL_AMA == dev->dev_type){
		pg_usbDev = 0;
		return ACE_STATUS_OK;
	}
	return ACE_STATUS_BAD_PARAM;
}
static int32_t usbcdc_write(const aceAuxDevHal_device_t* dev, const void* buf, size_t buf_size, uint32_t timeout_ms, uint32_t flags){
	if(pg_usbDev && pg_usbDev == dev){
		return usbcdc_sendAMAData(buf, buf_size, timeout_ms);
	}
	return 0;
}


///////////// AMAZON AMA interface /////  MOVE to file: hal_auxdev.c //////////
void usbcdc_hotplug_cb_trigger(void){
	static int pluged;
	if(g_usbcdc_hotPlgCb && !pluged){
		pluged = 1;
		g_usbcdc_hotPlgCb(&g_usbDev, ACE_AUXDEV_EV_DEV_ADDED, ACE_AUXDEV_HAL_AMA, 0);
	}
}

void usbcdc_notify_cb_trigger(aceAuxDevHal_event_t reason, u8 *data, size_t data_len){
	if(pg_usbDev && g_usbcdc_notifyCb){
		if(ACE_AUXDEV_EV_DEV_REMOVED == reason){
			g_usbcdc_notifyCb(pg_usbDev, ACE_AUXDEV_EV_DEV_REMOVED, ACE_STATUS_OK, 0, 0, 0);
			aceAuxDevHal_deinit();
		}else if(ACE_AUXDEV_EV_READ_RESP == reason){
		    g_usbcdc_notifyCb(pg_usbDev, ACE_AUXDEV_EV_READ_RESP, ACE_STATUS_OK, data, data_len, 0);
		}else if(ACE_AUXDEV_EV_WRITE_RESP == reason){
		    g_usbcdc_notifyCb(pg_usbDev, ACE_AUXDEV_EV_WRITE_RESP, ACE_STATUS_OK, data, data_len, 0);
		}
	}
}

ace_status_t aceAuxDevHal_registerHotplugCallback(aceAuxDevHal_type_t dev_type, aceAuxDevHal_hotPlugCb_t cb, const void* ctx){
	if(ACE_AUXDEV_HAL_AMA == dev_type){
		g_usbcdc_hotPlgCb = cb;
	}
	return 0;
}

ace_status_t aceAuxDevHal_init(void){
	if(!usbcdc_initialize){
		usbcdc_initialize = 1;
	}
	return ACE_STATUS_OK;
}

ace_status_t aceAuxDevHal_deinit(){
	aceAuxDevHal_deregisterNotify(pg_usbDev);
	usbcdc_close(pg_usbDev);
	usbcdc_initialize = g_usbcdc_hotPlgCb = 0;
	return 0;
}

ace_status_t aceAuxDevHal_getDevices(aceAuxDevHal_type_t dev_type, const aceAuxDevHal_device_t** devs, uint32_t* count){
	if(pg_usbDev){
		if(ACE_AUXDEV_HAL_AMA == dev_type){
			*devs = pg_usbDev;
			*count = 1;
			return ACE_STATUS_OK;
		}
	}
    return ACE_STATUS_NOT_FOUND;
}

ace_status_t aceAuxDevHal_getDeviceTypes(const aceAuxDevHal_type_t** dev_types, uint32_t* count){
	if(pg_usbDev){
		*dev_types = ACE_AUXDEV_HAL_AMA;
		*count = 1;
		return ACE_STATUS_OK;
	}
    return ACE_STATUS_NOT_FOUND;
}


////////////////////   client app codes below ////////////
/** Write function pointer. */
ace_status_t usbcdc_hot_plug_cb(const aceAuxDevHal_device_t* dev, aceAuxDevHal_event_t ev, aceAuxDevHal_type_t dev_type, const void* ctx){
    return ACE_STATUS_OK;
}

void ama_usb_init(){
    ace_status_t status = aceAuxDevHal_init();
    if(status != ACE_STATUS_OK){
      return;
    }
    aceAuxDevHal_registerHotplugCallback(ACE_AUXDEV_HAL_AMA, usbcdc_hot_plug_cb, NULL);
}




/* ///////////////////////////////////////////////////////////// */
//// 
// 1, ama_usb_init();
// 2, aceAuxDevHal_registerHotplugCallback
// 3, usbcdc_registerNotify
// 4, usbcdc_open()

#include "unity.h"
#include "unity_fixture.h"
#include "iot_test_ble_hal_common.h"
#define AMA_TESTS_WAIT                        60000                 /* Wait 60s max */
#define eAMAEventHotplugCb		10
#define eAMAEventNotifyCb		11

typedef struct{
    BLEHALEventsInternals_t xEvent;
    void* pHandle;
    BTStatus_t xStatus;
} AMATestHotplugCallback_t;

typedef struct{
    BLEHALEventsInternals_t xEvent;
    void* pHandle;
	aceAuxDevHal_event_t reason;
    BTStatus_t xStatus;
	size_t datalen;
} AMATestNotifyCallback_t;


static aceAuxDevHal_device_t *p_dev;
void prvAMAHotPlugSentCb( void* pHandle, BTStatus_t xStatus ){

    AMATestHotplugCallback_t * pCallback = IotTest_Malloc( sizeof( AMATestHotplugCallback_t ) );
    if( pCallback != NULL )
    {
        pCallback->xEvent.xEventTypes = eAMAEventHotplugCb;
        pCallback->xEvent.lHandle = NO_HANDLE;
        pCallback->pHandle = pHandle;
        pCallback->xStatus = xStatus;
        pushToQueue( &pCallback->xEvent.eventList );
    }
}
void prvAMANotifyCb( void* pHandle, aceAuxDevHal_event_t reason, BTStatus_t xStatus, size_t datalen ){

    AMATestNotifyCallback_t * pCallback = IotTest_Malloc( sizeof( AMATestNotifyCallback_t ) );
    if( pCallback != NULL )
    {
        pCallback->xEvent.xEventTypes = eAMAEventNotifyCb;
        pCallback->xEvent.lHandle = NO_HANDLE;
        pCallback->pHandle = pHandle;
		pCallback->reason = reason;
        pCallback->xStatus = xStatus;
		pCallback->datalen = datalen;
        pushToQueue( &pCallback->xEvent.eventList );
    }
}

ace_status_t aceTestAuxHotPlug_cb(const aceAuxDevHal_device_t* dev, aceAuxDevHal_event_t ev, aceAuxDevHal_type_t dev_type, const void* ctx){
	p_dev = dev;
	prvAMAHotPlugSentCb(dev, ACE_STATUS_OK);
}

ace_status_t aceTestAuxDevHal_notifyCb(const aceAuxDevHal_device_t* dev, aceAuxDevHal_event_t reason,
										ace_status_t status, void* data, size_t data_len, const void* ctx){
										
	prvAMANotifyCb(dev, reason, status, data_len);
}

static void amausb_testcase_setup(void) {
	testcase_setup();
}
static ace_status_t amausb_testcase_teardown(void) {
	return testcase_teardown();
}

static test_AMA_USB_teardown(void){
	amausb_testcase_setup();
	ace_status_t status = aceAuxDevHal_deinit();
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, status );
	amausb_testcase_teardown();
}

/*-----------------------------------------------------------*/
void test_AMA_USB_setup(){
    AMATestHotplugCallback_t xEvent;
	amausb_testcase_setup();

	ama_usb_init();
    aceAuxDevHal_registerHotplugCallback(ACE_AUXDEV_HAL_AMA, aceTestAuxHotPlug_cb, NULL);
    int xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventHotplugCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestHotplugCallback_t ), AMA_TESTS_WAIT );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xStatus );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xEvent.xStatus );
    TEST_ASSERT_NOT_EQUAL( 0, xEvent.pHandle);
    TEST_ASSERT_NOT_EQUAL( 0, p_dev);
	xStatus = p_dev->common_api->registerNotify(p_dev, aceTestAuxDevHal_notifyCb, 0);
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xStatus );
	xStatus = usbcdc_open(p_dev);
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xStatus );
	amausb_testcase_teardown();
}

static u8 amaTestData[] = {'a','m','a',' ','T','e','s','t','1','2','3','4','5'};
void test_AMA_USB_Loopback(){
    AMATestNotifyCallback_t xEvent;
	amausb_testcase_setup();

    TEST_ASSERT_NOT_EQUAL( 0, p_dev);
	p_dev->common_api->write(p_dev, amaTestData, sizeof(amaTestData), 0, 0);
    int xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xStatus );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xEvent.xStatus );
    TEST_ASSERT_EQUAL( xEvent.pHandle, p_dev);
	TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_WRITE_RESP, xEvent.reason);
	int len = xEvent.datalen;
	if(len < sizeof(amaTestData)){
		xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
		TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_WRITE_RESP, xEvent.reason);
		len += xEvent.datalen;
	}
	TEST_ASSERT_EQUAL( sizeof(amaTestData), len);
    xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xStatus );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xEvent.xStatus );
    TEST_ASSERT_EQUAL( xEvent.pHandle, p_dev);
	TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_READ_RESP, xEvent.reason);
	len = xEvent.datalen;
	if(len < sizeof(amaTestData)){
		xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
		TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_READ_RESP, xEvent.reason);
		len += xEvent.datalen;
	}
	TEST_ASSERT_EQUAL( sizeof(amaTestData), len);
	amausb_testcase_teardown();
}

#define AMA_TEST_LONG_MAX		80
static u8 amaTestLongData[AMA_TEST_LONG_MAX];
void test_AMA_USB_write_notify_long(){
    AMATestNotifyCallback_t xEvent;
	amausb_testcase_setup();

	for(int i=0;i<80;++i){
		amaTestLongData[i]=i;
	}

    TEST_ASSERT_NOT_EQUAL( 0, p_dev);
	p_dev->common_api->write(p_dev, amaTestLongData, sizeof(amaTestLongData), 0, 0);
    int xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xStatus );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xEvent.xStatus );
    TEST_ASSERT_EQUAL( xEvent.pHandle, p_dev);
	TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_WRITE_RESP, xEvent.reason);
	int len = xEvent.datalen;
    xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
	TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_WRITE_RESP, xEvent.reason);
	len += xEvent.datalen;
	if(len < sizeof(amaTestLongData)){
		xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
		TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_WRITE_RESP, xEvent.reason);
		len += xEvent.datalen;
	}
	TEST_ASSERT_EQUAL( sizeof(amaTestLongData), len);

	// please send data with length of AMA_TEST_LONG_MAX
    xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xStatus );
    TEST_ASSERT_EQUAL( ACE_STATUS_OK, xEvent.xStatus );
    TEST_ASSERT_EQUAL( xEvent.pHandle, p_dev);
	TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_READ_RESP, xEvent.reason);
	len = xEvent.datalen;
    xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
	TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_READ_RESP, xEvent.reason);

	len += xEvent.datalen;
	if(len < sizeof(amaTestLongData)){
		xStatus = IotTestBleHal_WaitEventFromQueue( eAMAEventNotifyCb, NO_HANDLE, ( void * ) &xEvent, sizeof( AMATestNotifyCallback_t ), AMA_TESTS_WAIT );
		TEST_ASSERT_EQUAL( ACE_AUXDEV_EV_READ_RESP, xEvent.reason);
		len += xEvent.datalen;
	}

	TEST_ASSERT_EQUAL( sizeof(amaTestLongData), len);
	amausb_testcase_teardown();
}

int amausb_tests_main(int argc, const char* argv[]) {
    aceIntegrationTestWrapper_begin(argc, argv, "amausb_tests_", NULL, NULL, NULL, NULL);

    RUN_TEST(test_AMA_USB_setup, __LINE__, RUN_TEST_NO_ARGS);
    RUN_TEST(test_AMA_USB_Loopback, __LINE__, RUN_TEST_NO_ARGS);
    RUN_TEST(test_AMA_USB_write_notify_long, __LINE__, RUN_TEST_NO_ARGS);
    RUN_TEST(test_AMA_USB_teardown, __LINE__, RUN_TEST_NO_ARGS);
    return (aceIntegrationTestWrapper_end());
}

//  test case:
// 0:  test init
// 1, plug in , un-plug
// 2, send data
// 3, receive data
// 4, loopback
// 5, send too many data

#endif


